package cn.harry.modular.sys.service.impl;

import cn.harry.common.annotation.DataScope;
import cn.harry.common.constant.CommonConstant;
import cn.harry.common.exception.ApiException;
import cn.harry.common.utils.IpUtil;
import cn.harry.common.utils.JwtTokenUtil;
import cn.harry.common.utils.SecurityUtils;
import cn.harry.modular.sys.mapper.SysUserMapper;
import cn.harry.modular.sys.domain.SysUser;
import cn.harry.modular.sys.enums.CacheConstants;
import cn.harry.modular.sys.service.SysUserLoginLogService;
import cn.harry.modular.sys.service.SysUserRoleService;
import cn.harry.modular.sys.service.SysUserService;
import cn.harry.modular.sys.vo.LoginResult;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * 后台用户表
 *
 * @author honghh Date 2019-10-12 10:55:44 Copyright (C) www.tech-harry.cn
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>
		implements SysUserService {

	private final JwtTokenUtil jwtTokenUtil;

	private final SysUserRoleService sysUserRoleService;

	private final SysUserLoginLogService sysUserLoginLogService;

	private final AuthenticationManager authenticationManager;

	private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();

	private final CacheManager cacheManager;

	@Override
	public SysUser getByUsername(String username) {
		return getOne(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getUsername, username));
	}

	@Override
	@CacheEvict(value = CacheConstants.USER_DETAILS, key = "#username")
	public LoginResult login(String username, String password) {
		LoginResult res = new LoginResult();
		String token;
		// 密码需要客户端加密后传递
		try {
			// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
			Authentication authenticate = authenticationManager.authenticate(
					new UsernamePasswordAuthenticationToken(username, password));
			SecurityContextHolder.getContext().setAuthentication(authenticate);
			token = jwtTokenUtil.generateToken(authenticate.getName());
			SysUser sysUser = SecurityUtils.getSysUser();

			// 更新最后一次登录时间
			updateLastLoginInfo(sysUser);
			// 记录登录日志
			sysUserLoginLogService.recordLoginInfo(sysUser, token,
					jwtTokenUtil.getExpiration());

			res.setToken(token);
			res.setTokenHead(jwtTokenUtil.getTokenHead());
			res.setExpiration(jwtTokenUtil.getExpiration());

			return res;
		}
		catch (Exception e) {
			log.error("登录异常:{}", e.getMessage());
			throw new ApiException(e.getMessage());
		}
	}

	/**
	 * 更新最后一次登录时间/登录IP
	 * @param sysUser
	 */
	private void updateLastLoginInfo(SysUser sysUser) {
		sysUser.setLoginIp(IpUtil.getIp());
		sysUser.setLoginTime(DateUtil.date());
		updateById(sysUser);
	}

	@Override
	public String refreshToken(String oldToken) {
		String token = oldToken.substring(jwtTokenUtil.getTokenHead().length());
		if (jwtTokenUtil.canRefresh(token)) {
			return jwtTokenUtil.refreshToken(token);
		}
		return null;
	}

	@Override
	public SysUser getUserById(Long userId) {
		SysUser user = getById(userId);
		if (userId != null) {
			// 获取用户所属的角色列表
			List<Long> roleIdList = sysUserRoleService.listRoleIdByUserId(userId);
			user.setRoleIds(roleIdList);
		}
		return user;
	}

	@Override
	public int update(Long id, SysUser user) {
		removeCache(id);
		user.setId(id);
		return this.baseMapper.updateById(user);
	}

	@Override
	@DataScope(userId = "id")
	public IPage<SysUser> getPage(Map<String, Object> params) {
		int pageSize = Integer.parseInt(String.valueOf(params.get("size")));
		int pageNum = Integer.parseInt(String.valueOf(params.get("current")));
		LambdaQueryWrapper<SysUser> wrapper = createWrapper(params);
		return page(new Page<>(pageNum, pageSize), wrapper);
	}

	@Override
	public int updateUserAndRole(SysUser user) {
		// 先删除用户与角色关系
		sysUserRoleService.delAndCreateRole(user.getId(), user.getRoleIds());
		// 更新用户
		return this.baseMapper.updateById(user);
	}

	@Override
	public boolean updatePasswordByUserId(Long userId, String newPassword) {
		removeCache(userId);
		// 新密码
		String newPass = ENCODER.encode(newPassword);
		SysUser user = new SysUser();
		user.setPassword(newPass);
		user.setId(userId);
		return updateById(user);
	}

	@Override
	public boolean updateStatus(Long id, String status) {

		removeCache(id);

		SysUser user = new SysUser();
		user.setId(id);
		user.setStatus(status);
		return updateById(user);
	}

	private void removeCache(Long userId) {
		SysUser user = getById(userId);
		if (user != null) {
			String username = user.getUsername();
			Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS);
			if (cache != null) {
				cache.evict(username);
			}
		}
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean insertUserAndRole(SysUser user) {
		boolean result = save(user);
		// 保存用户与角色关系
		if (CollUtil.isNotEmpty(user.getRoleIds())) {
			sysUserRoleService.insertUserAndUserRole(user.getId(), user.getRoleIds());
		}
		return result;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean deleteByIds(Long[] ids) {
		for (Long id : ids) {
			removeCache(id);
			// 删除用户与角色关系
			sysUserRoleService.delAndCreateRole(id, null);
		}
		return removeByIds(Arrays.asList(ids));
	}

	@Override
	public List<SysUser> getExportList(Map<String, Object> params) {
		LambdaQueryWrapper<SysUser> wrapper = createWrapper(params);

		return list(wrapper);
	}

	private LambdaQueryWrapper<SysUser> createWrapper(Map<String, Object> params) {
		String username = (String) params.get("username");
		String phone = (String) params.get("phone");
		String deptId = (String) params.get("deptId");
		String beginTime = (String) params.get("beginTime");
		String endTime = (String) params.get("endTime");
		String status = (String) params.get("status");

		LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
		wrapper.like(StrUtil.isNotEmpty(username), SysUser::getUsername, username);
		wrapper.like(StrUtil.isNotEmpty(phone), SysUser::getPhone, phone);
		wrapper.apply(StrUtil.isNotEmpty(deptId), "(dept_id = " + deptId
				+ " OR dept_id IN ( SELECT t.id FROM sys_dept t WHERE FIND_IN_SET ( "
				+ deptId + " , ancestors ) ))");
		wrapper.gt(StrUtil.isNotEmpty(beginTime), SysUser::getCreateTime, beginTime);
		wrapper.lt(StrUtil.isNotEmpty(endTime), SysUser::getCreateTime, endTime);
		wrapper.eq(StrUtil.isNotEmpty(status), SysUser::getStatus, status);
		wrapper.apply(params.get(CommonConstant.SQL_FILTER) != null,
				(String) params.get(CommonConstant.SQL_FILTER));

		return wrapper;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	@CacheEvict(value = CacheConstants.USER_DETAILS, key = "#user.username")
	public boolean updateUser(SysUser user) {
		if (getDbUser(user))
			return false;
		int count = updateUserAndRole(user);
		return count > 0;
	}

	private boolean getDbUser(SysUser user) {
		String password = user.getPassword();
		SysUser dbUserNameInfo = getByUsername(user.getUsername());
		if (dbUserNameInfo != null
				&& !Objects.equals(dbUserNameInfo.getId(), user.getId())) {
			return true;
		}
		if (StrUtil.isBlank(password)) {
			user.setPassword(null);
		}
		else {
			user.setPassword(ENCODER.encode(user.getPassword()));
		}
		return false;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean createUser(SysUser user) {
		if (getDbUser(user))
			return false;
		return insertUserAndRole(user);
	}

}